﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BacteriaSimulation.Simulation
{

    public enum BacteriumState
    {
        ALIVE, DEAD
    }

    public class Bacterium
    {
        public delegate void BacteriumUpdateEventHandler(object sender, BacteriumUpdateEventArgs args);
        public delegate void BacteriumDeathEventHandler(BacteriumUpdateEventArgs args);

        public event BacteriumUpdateEventHandler BacteriumUpdate;
        public event BacteriumDeathEventHandler Death;

        // Strategy object that encapsulate the behaviour of this bacterium
        public BacteriumSimulationStrategy Strategy { get; set; }

        // Living energy of the bacterimum
        public Double Energy { get; set; }

        /// <summary>
        /// Maximum energy this bacterium can reach before duplicating himself
        /// </summary>
        public Double MaxEnergy { get; set; }

        /// <summary>
        ///  Current speed vector of this bacterium
        /// </summary>
        public Vector2D Speed { get; set; }

        /// <summary>
        /// Current speed magnitude of this bacterium
        /// </summary>
        public Double SpeedMagnitude { get; set; }

        /// <summary>
        /// Current direction this bacterium wants to go to
        /// </summary>
        public UnitVector2D AccelerationDirection { get; set; }
        /// <summary>
        /// The acceleration that this bacterium is able to apply to himself
        /// </summary>
        public Double AccelerationMagnitude { get; set; }

        public Double MaxSpeedComponent { get; set; }
        public SimulationPoint Position { get; set; }
        public Double Width { get; set; }
        public Double Height { get; set; }
        public BacteriumState State { get; set; }
        public Double WidthIncrement { get; set; }
        public Double HeightIncrement { get; set; }
        public Double EnergyIncrement { get; set; }
        private Simulation simulation;

        /// <summary>
        /// Tells if overlapping with other bacteria is temporarily allowed for this Bacterium
        /// </summary>
        public bool AllowTeporaryOvelap { get; set; }
        public Bacterium Brother { get; set; }

        public IndexGridCell Cell { get; set; }

        public Double X
        {
            get
            {
                return this.Position.X;
            }
            
            set
            {
                this.Position.X = value;
            }

        }

        public Double Y
        {
            get
            {
                return this.Position.Y;
            }
            set
            {
                this.Position.Y = value;
            }
        }

        public Bacterium(Simulation simulation, SimulationPoint position, Vector2D direction, Double speedModule, Double energy, BacteriumSimulationStrategy strategy, Double maxEnergy,
            Double energyIncrement, Bacterium brother)
        {
            this.AccelerationMagnitude = 1000;

            this.simulation = simulation;
            this.Width = Math.Sqrt(energy / Math.PI) * 2;
            this.Height = this.Width;
            this.Position = position;

            this.SpeedMagnitude = speedModule;
            UnitVector2D directionUnitVector = new UnitVector2D(direction.XComponent, direction.YComponent);
            //this.Direction = directionUnitVector;
            Double speedXComponent = directionUnitVector.XComponent * SpeedMagnitude;
            Double speedyComponent = directionUnitVector.YComponent * SpeedMagnitude;
            this.Speed = new Vector2D(speedXComponent, speedyComponent);

            this.Energy = energy;
            this.Strategy = strategy;
            this.MaxEnergy = maxEnergy;
            this.State = BacteriumState.ALIVE;
            this.EnergyIncrement = energyIncrement;
            this.AllowTeporaryOvelap = true;
            this.Brother = brother;
        }

        public Bacterium(Simulation simulation, SimulationPoint position, Vector2D direction, Double speedModule, Double energy, BacteriumSimulationStrategy strategy, Double maxEnergy,
            Double energyIncrement)
            : this(simulation, position, direction, speedModule, energy, strategy, maxEnergy, energyIncrement, null)
        {
        }
            
        public Bacterium(Simulation simulation, SimulationPoint position, Vector2D direction, Double speedModule, Double energy, BacteriumSimulationStrategy strategy)
            : this(simulation, position, direction, speedModule, energy, strategy, SimulationEnvironment.MAX_ENERGY, SimulationEnvironment.ENERGY_INCREMENT, null)
        {
        }

        protected void OnBacteriumUpdate(BacteriumUpdateEventArgs args)
        {
            if(BacteriumUpdate != null)
            {
                BacteriumUpdate(this, args);
            }
        }

        protected void OnDeath()
        {
            if (Death != null)
            {
                BacteriumUpdateEventArgs args = new BacteriumUpdateEventArgs(this);
                Death(args);
            }
        }

        public void NotifyDeath()
        {
            OnDeath();
        }

        public void PerformSimulationStep(SimulationEnvironment env)
        {
            Strategy.PerformSimulationStep(this, env);
        }

        public void WallCollisionDetected(WALL_COLLISION_TYPE collisionType)
        {
            Strategy.WallCollisionDetected(this, collisionType);
        }

        public void BacteriumCollisionDetected(Bacterium otherBacterium, SimulationEnvironment env)
        {
            this.Strategy.BacteriumCollisionDetected(this, otherBacterium, env);
        }

        public void Grow(Double energyIncrement, SimulationEnvironment env)
        {
            this.Energy += energyIncrement;
            env.AvalaibleEnergyResource -= energyIncrement;
            adjustRadius();
        }

        private void adjustRadius()
        {
            Double radiusIncrement = Math.Sqrt(this.Energy / Math.PI) - (this.Width / 2);
            this.Width += (radiusIncrement * 2);
            this.Height += (radiusIncrement * 2);
        }

        public void EatAndGrow(Bacterium eatenBacterium)
        {
            this.Energy += eatenBacterium.Energy;
            adjustRadius();
        }

        public void UnGrow(Double energyDecrement, SimulationEnvironment env)
        {
            this.Energy -= energyDecrement;
            env.AvalaibleEnergyResource += energyDecrement;
            Double radiusDecrement = -1 * Math.Sqrt(this.Energy / Math.PI) + (this.Width / 2);
            this.Width -= radiusDecrement;
            this.Height -= radiusDecrement;
            if (this.Width <= 0)
            {
                this.Width = SimulationEnvironment.EPSILON;
            }
            if (this.Height <= 0)
            {
                this.Height = SimulationEnvironment.EPSILON;
            }
        }

        public Bacterium Clone()
        {
            Bacterium clone = new Bacterium(this.simulation, this.Position.Clone(), this.Speed.Clone(), this.SpeedMagnitude, this.Energy, null, this.MaxEnergy,
                this.EnergyIncrement);
            return clone;
        }

        public View See()
        {
           
            View view = new View();
            if (this.Cell != null)
            {
                view.addCell(this.Cell);
                List<IndexGridCell> adjacent = simulation.Grid.GetAdjacentCells(this.Cell.RowIndex, this.Cell.ColumnIndex);
                view.addAllCells(adjacent);
            }
                
            return view;
            
        }

        public void AdjustPosition(SimulationEnvironment env)
        {
            if (this.X > (env.Width - this.Width))
            {
                this.X = env.Width - this.Width - env.Epsilon;
            }
            if (this.X < 0)
            {
                this.X = 0 + env.Epsilon;
            }
            if (this.Y > (env.Height - this.Height))
            {
                this.Y = (env.Height - this.Height - env.Epsilon);
            }
            if (this.Y < 0)
            {
                this.Y = 0 + env.Epsilon;
            }
        }
    }
}
